#ifndef __M_THRPOOL_H__
#define __M_THRPOOL_H__
#include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <functional>
#include <mutex>
#include <condition_variable>


class ThreadPool
{
public:
    using ptr = std::shared_ptr<ThreadPool>;
    using Functor = std::function<void()>;
    ThreadPool(int th_count = 1)
    : _stop(false)
    {
        for (int i = 0; i < th_count; i++)
            _threads.emplace_back(&ThreadPool::entry, this);
    }
    ~ThreadPool()
    {
        stop();
    }

    void stop()
    {
        if (_stop == true) return;

        // 唤醒所有线程，处理掉手头的任务，等待线程
        _stop = true;
        _cv.notify_all();
        for (auto& th : _threads)
            th.join();
    }

    // 传入一个任务函数和它的参数，但是函数类型和参数都不是固定的
    // push 函数内部，将传入的函数封装为一个异步任务(packaged_task)，并返回一个 future 对象
    // 使用 lambda 捕获异步任务，生成一个匿名可调用对象，抛入任务池，等待工作线程执行
    template<class Fn, class... Args>
    auto push(Fn&& func, Args&&... args) -> std::future<decltype(func(args...))>
    {
        // 1.进行packaged_task 需要知道函数的 返回类型和参数
        // 返回类型使用 decltype 推导
        using return_type = decltype(func(args...));
        // 参数：使用 bind 将函数和参数绑定，二次封装。这样使用 future 时就可以不传参数了
        auto tmp_func = std::bind(std::forward<Fn>(func), std::forward<Args>(args)...);

        // 进行 packaged，由于不能直接像函数那样使用，所以使用指针访问
        auto ptask = std::make_shared<std::packaged_task<return_type()>>(tmp_func);
        std::future<return_type> fu = ptask->get_future();

        // 2.lambda 捕获任务对象，构造一个匿名可调用对象，抛入任务池
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _taskpoll.push_back([ptask](){
                (*ptask)();
            });
            _cv.notify_one();
        }

        return fu;
    }
private:
    // 线程入口函数，不断从任务池中取出任务并执行
    void entry()
    {
        while(1)
        {
            std::vector<Functor> tmp_taskpool;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                // 线程池停止而且所有任务都被处理了，就可以停止了
                if (_stop && _taskpoll.empty()) break;

                // 等待，唤醒条件：线程池不为空，或者线程池停止
                _cv.wait(lock, [this](){
                    return this->_stop || !this->_taskpoll.empty();
                });

                // 取出任务
                tmp_taskpool.swap(_taskpoll);
            }
            // 执行任务
            for (auto& task : tmp_taskpool)
                task();
        }
    }
private:
    std::atomic<bool> _stop; // 线程池的状态
    std::vector<Functor> _taskpoll; // 任务池，线程从这里取出任务并执行
    // 互斥锁+条件变量：实现同步与互斥
    std::mutex _mutex;
    std::condition_variable _cv;
    std::vector<std::thread> _threads; // 管理所有的线程
};

#endif